﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Mime
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    using Mews.Text;

    internal enum LineType
    {
        Content,
        Boundary,
        ClosingBoundary
    }

    /// <summary>Processes the body of a multipart entity.</summary>
    internal sealed class MultipartBodyProcessor : StateMachine<ArraySegment<byte>, int>, ILineProcessor<Body>
    {
        private readonly bool isDigest;
        private readonly MultipartBody result;
        private readonly ArraySegment<byte> closeDelimiter;
        private readonly int closeDelimiterPastEnd;
        private readonly UnstructuredTextProcessor preambleProcessor =
            new UnstructuredTextProcessor(EncodingTransformation.Identity, Encoding.ASCII);

        private BodyPartProcessor currentProcessor;
        private readonly UnstructuredTextProcessor epilogueProcessor =
            new UnstructuredTextProcessor(EncodingTransformation.Identity, Encoding.ASCII);

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="ILineProcessor{T}.ProcessFirstLine"/>.</summary>
        public int ProcessFirstLine(ArraySegment<byte> segment)
        {
            return this.ProcessEvent(segment);
        }

        /// <summary>See <see cref="ILineProcessor{T}.GetResult"/>.</summary>
        public Body GetResult()
        {
            this.AddBodyPartIfValid();
            this.result.Preamble = this.preambleProcessor.GetText();
            this.result.Epilogue = this.epilogueProcessor.GetText();
            return this.result;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal MultipartBodyProcessor(Disposition disposition, FallbackReason fallbackReason, string boundary) :
            this(boundary)
        {
            this.result = new MultipartBody(fallbackReason);
            this.result.Disposition = disposition;
        }

        internal MultipartBodyProcessor(string subtype, Disposition disposition, string boundary) : this(boundary)
        {
            this.isDigest = subtype == MultipartSubtypeNames.Digest;
            this.result = new MultipartBody(subtype);
            this.result.Disposition = disposition;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private MultipartBodyProcessor(string boundary)
        {
            this.closeDelimiter = new ArraySegment<byte>(Encoding.ASCII.GetBytes("\r\n--" + boundary + "--"));
            this.closeDelimiterPastEnd = this.closeDelimiter.Offset + this.closeDelimiter.Count;
            this.State = this.InPreamble;
        }

        private int InPreamble(ArraySegment<byte> ev)
        {
            int offset;

            switch (this.GetFirstLineType(ev, out offset))
            {
                case LineType.Boundary:
                    this.CreateNewBodyPartProcessor();
                    this.State = this.InBodyPart;
                    return offset;
                case LineType.ClosingBoundary:
                    this.State = this.InEpilogue;
                    return offset;
                default:
                    return this.preambleProcessor.ProcessFirstLine(ev);
            }
        }

        private int InBodyPart(ArraySegment<byte> ev)
        {
            int offset;

            switch (this.GetFirstLineType(ev, out offset))
            {
                case LineType.Boundary:
                    this.AddBodyPartIfValid();
                    this.CreateNewBodyPartProcessor();
                    return offset;
                case LineType.ClosingBoundary:
                    this.AddBodyPartIfValid();
                    this.State = this.InEpilogue;
                    return offset;
                default:
                    return this.currentProcessor.ProcessEvent(ev);
            }
        }

        private int InEpilogue(ArraySegment<byte> ev)
        {
            return this.epilogueProcessor.ProcessFirstLine(ev);
        }

        private void CreateNewBodyPartProcessor()
        {
            this.currentProcessor = new BodyPartProcessor(this.isDigest);
        }

        private void AddBodyPartIfValid()
        {
            if (this.currentProcessor != null)
            {
                this.result.Parts.Add(this.currentProcessor.GetResult());
                this.currentProcessor = null;
            }
        }

        private LineType GetFirstLineType(ArraySegment<byte> segment, out int lineIndex)
        {
            int closeDelimiterIndex;
            GetFirstNonMatchingIndexes(segment, this.closeDelimiter, out lineIndex, out closeDelimiterIndex);

            if (closeDelimiterIndex == this.closeDelimiterPastEnd)
            {
                IgnoreTrailingBoundaryLineCharacters(segment, ref lineIndex);
                return LineType.ClosingBoundary;
            }
            else if (closeDelimiterIndex == this.closeDelimiterPastEnd - 2)
            {
                IgnoreTrailingBoundaryLineCharacters(segment, ref lineIndex);
                return LineType.Boundary;
            }
            else
            {
                return LineType.Content;
            }
        }

        private static void IgnoreTrailingBoundaryLineCharacters(ArraySegment<byte> segment, ref int lineIndex)
        {
            int pastEnd = segment.Offset + segment.Count;

            // Anything after the boundary and before the next linebreak is to be ignored, see
            // http://www.ietf.org/rfc/rfc2046.txt, 5.1.1./10
            for (; (lineIndex < pastEnd) && (segment.Array[lineIndex] != (byte)'\r'); ++lineIndex)
            {
            }
        }

        /// <summary>Starting with <see cref="ArraySegment{T}.Offset"/> of each array segment, compares the elements of
        /// the passed array segments and sets the indexes where the elements don't match for the first time.</summary>
        /// <remarks>If all elements of an array segment have been found equal to the ones of the other segment then the
        /// associated index is set to <see cref="ArraySegment{T}.Offset"/> + <see cref="ArraySegment{T}.Count"/>.
        /// </remarks>
        private static void GetFirstNonMatchingIndexes(
            ArraySegment<byte> first, ArraySegment<byte> second, out int firstIndex, out int secondIndex)
        {
            int firstPastEnd = first.Offset + first.Count;
            int secondPastEnd = second.Offset + second.Count;

            for (firstIndex = first.Offset, secondIndex = second.Offset;
                (firstIndex < firstPastEnd) && (secondIndex < secondPastEnd);
                ++firstIndex, ++secondIndex)
            {
                if (first.Array[firstIndex] != second.Array[secondIndex])
                {
                    return;
                }
            }
        }
    }
}
